package de.evernote;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.scribe.builder.ServiceBuilder;
import org.scribe.builder.api.EvernoteApi;
import org.scribe.model.Token;
import org.scribe.model.Verifier;
import org.scribe.oauth.OAuthService;

import com.evernote.auth.EvernoteAuth;
import com.evernote.auth.EvernoteService;
import com.evernote.clients.ClientFactory;
import com.evernote.clients.NoteStoreClient;
import com.evernote.clients.UserStoreClient;
import com.evernote.edam.error.EDAMNotFoundException;
import com.evernote.edam.error.EDAMSystemException;
import com.evernote.edam.error.EDAMUserException;
import com.evernote.edam.notestore.NoteFilter;
import com.evernote.edam.notestore.NoteMetadata;
import com.evernote.edam.notestore.NoteStore;
import com.evernote.edam.notestore.NotesMetadataList;
import com.evernote.edam.notestore.NotesMetadataResultSpec;
import com.evernote.edam.type.Notebook;
import com.evernote.edam.type.Tag;
import com.evernote.edam.userstore.AuthenticationResult;
import com.evernote.edam.userstore.UserStore;
import com.evernote.thrift.TException;
import com.evernote.thrift.protocol.TBinaryProtocol;
import com.evernote.thrift.transport.THttpClient;

@SuppressWarnings("serial")
public class EvernoteGAEServlet extends HttpServlet {

	static final String CONSUMER_KEY = "stefcom-6138";
	static final String CONSUMER_SECRET = "3af0fb6a86344b99";

	static final EvernoteService EVERNOTE_SERVICE = EvernoteService.PRODUCTION;
	static final String CALLBACK_URL = "evernotegae?action=callbackReturn";

	static final String csvSeparator = "\t";

	static final String userStoreUrl = "https://www.evernote.com/edam/user";
	static final String noteStoreUrlBase = "https://www.evernote.com/edam/note/";

	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {

		String accessToken = (String) req.getParameter("accessToken");
		String requestToken = (String) req.getSession().getAttribute(
				"requestToken");
		String requestTokenSecret = (String) req.getSession().getAttribute(
				"requestTokenSecret");
		String verifier = (String) req.getSession().getAttribute("verifier");
		String noteStoreUrl = (String) req.getSession().getAttribute(
				"noteStoreUrl");

		String action = req.getParameter("action");
		String type = req.getParameter("type");
		boolean isBusiness = false;
		if("business".equals(type)) {
			isBusiness = true;
		}
		
		if (accessToken == null) {
			accessToken = (String) req.getSession().getAttribute(
					"accessToken");
		} else {
			accessToken = accessToken.replaceAll("\"", "");
			req.getSession().setAttribute("accessToken", accessToken);			
		}

		accessToken = accessToken.replaceAll("\"", "");
		
		String thisUrl = req.getRequestURL().toString();
		String cbUrl = thisUrl.substring(0, thisUrl.lastIndexOf('/') + 1)
				+ CALLBACK_URL;

		PrintWriter out = resp.getWriter();

		Class<? extends EvernoteApi> providerClass = EvernoteApi.Sandbox.class;
		if (EVERNOTE_SERVICE == EvernoteService.PRODUCTION) {
			providerClass = org.scribe.builder.api.EvernoteApi.class;
		}
		OAuthService service = new ServiceBuilder().provider(providerClass)
				.apiKey(CONSUMER_KEY).apiSecret(CONSUMER_SECRET)
				.callback(cbUrl).build();

		try {
			if ("reset".equals(action)) {
				System.err.println("Resetting");
				// Empty the server's stored session information for the current
				// browser user so we can redo the test.
				for (Enumeration<?> names = req.getSession()
						.getAttributeNames(); names.hasMoreElements();) {
					req.getSession().removeAttribute(
							(String) names.nextElement());
				}
				accessToken = null;
				requestToken = null;
				requestTokenSecret = null;
				verifier = null;
				noteStoreUrl = null;
				out.println("Removed all attributes from user session");

			} else if ("getRequestToken".equals(action)) {
				// Send an OAuth message to the Provider asking for a new
				// Request
				// Token because we don't have access to the current user's
				// account.
				Token scribeRequestToken = service.getRequestToken();

				out.println("<br/><b>Reply:</b> <br/> <span style=\"word-wrap: break-word\">"
						+ scribeRequestToken.getRawResponse() + "</span>");
				requestToken = scribeRequestToken.getToken();
				requestTokenSecret = scribeRequestToken.getSecret();
				req.getSession().setAttribute("requestToken", requestToken);
				req.getSession().setAttribute("requestTokenSecret",
						requestTokenSecret);

			} else if ("getAccessToken".equals(action)) {
				// Send an OAuth message to the Provider asking to exchange the
				// existing Request Token for an Access Token
				Token scribeRequestToken = new Token(requestToken,
						requestTokenSecret);
				Verifier scribeVerifier = new Verifier(verifier);
				Token scribeAccessToken = service.getAccessToken(
						scribeRequestToken, scribeVerifier);
				EvernoteAuth evernoteAuth = EvernoteAuth.parseOAuthResponse(
						EVERNOTE_SERVICE, scribeAccessToken.getRawResponse());
				// out.println("<br/><b>Reply:</b> <br/> <span style=\"word-wrap: break-word\">"
				// + scribeAccessToken.getRawResponse() + "</span>");
				accessToken = evernoteAuth.getToken();
				noteStoreUrl = evernoteAuth.getNoteStoreUrl();
				req.getSession().setAttribute("accessToken", accessToken);
				req.getSession().setAttribute("noteStoreUrl", noteStoreUrl);

			} else if ("callbackReturn".equals(action)) {
				requestToken = req.getParameter("oauth_token");
				verifier = req.getParameter("oauth_verifier");
				req.getSession().setAttribute("verifier", verifier);

			} else if ("listNotebooks".equals(action)) {
				listNotebooks(isBusiness, resp, accessToken, out);
			}

		} catch (Exception e) {
			e.printStackTrace();
			out.println(e.toString());
		}

		// Step 1 in OAuth authorization: obtain an unauthorized request token
		// from the provider
		if (requestToken == null && accessToken == null) {
			URL url = new URL("http://" + req.getServerName() + ":"
					+ req.getServerPort() + req.getServletPath()
					+ "?action=getRequestToken");
			System.out.println("getRequestToken url: " + url);
			// urlFetchService.fetch(url);
			resp.sendRedirect("http://" + req.getServerName() + ":"
					+ req.getServerPort() + req.getServletPath()
					+ "?action=getRequestToken");

			// out.println("<a href='?action=getRequestToken'>Get OAuth Request Token from Provider</a>");
		} else {
			// out.println("Get OAuth Request Token from Provider");
		}

		// Step 2 in OAuth authorization: redirect the user to the provider to
		// authorize the request token
		if (requestToken != null && verifier == null && accessToken == null) {
			URL url = new URL(
					EVERNOTE_SERVICE.getAuthorizationUrl(requestToken));
			System.out.println("getAuthorizationUrl: " + url);
			resp.sendRedirect(EVERNOTE_SERVICE
					.getAuthorizationUrl(requestToken));
		} else {
			// out.println("Send user to get authorization");
		}

		// Step 3 in OAuth authorization: exchange the authorized request
		// token for an access token

		if (requestToken != null && verifier != null && accessToken == null) {
			URL url = new URL("http://" + req.getServerName() + ":"
					+ req.getServerPort() + req.getServletPath()
					+ "?action=getAccessToken");
			System.out.println("getAccessToken: " + url);

			resp.sendRedirect("http://" + req.getServerName() + ":"
					+ req.getServerPort() + req.getServletPath()
					+ "?action=getAccessToken");

			// out.println("<a href='?action=getAccessToken'> Get OAuth Access Token from Provider </a>");
		} else {
			// out.println("Get OAuth Access Token from Provider");
		}

		// Step 4 in OAuth authorization: use the access token that you
		// obtained
		// In this sample, we simply list the notebooks in the user's
		// Evernote account

		if (accessToken != null && !("listNotebooks".equals(action))) {
			resp.setContentType("text/html");
			out.println("OAuth-Access-Token: " + accessToken);
			// out.println("<a href='?action=listNotebooks'>List notebooks in account</a><br />");
		} else {
			// out.println("List notebooks in account");
		}

		// out.println("<a href='?action=reset'>Reset user session</a>");

	}

	private void listNotebooks(boolean isBusiness, HttpServletResponse resp, String accessToken,
			PrintWriter out) throws EDAMUserException, EDAMSystemException,
			TException, EDAMNotFoundException, Exception {
		resp.setContentType("text/csv");

		HashMap<String, String> tags = new HashMap<String, String>();
		HashMap<String, String> notebookNames = new HashMap<String, String>();

		MyNoteStoreClient noteStoreClient;

		if (isBusiness) {
			noteStoreClient = new BusinessNoteStoreClient(accessToken);
		} else {
			EvernoteAuth evernoteAuth = new EvernoteAuth(EVERNOTE_SERVICE,
					accessToken);
			noteStoreClient = new CloudNoteStoreClient(evernoteAuth);
		}

		NotesMetadataResultSpec resultSpec = new NotesMetadataResultSpec();
		resultSpec.setIncludeAttributes(true);
		resultSpec.setIncludeCreated(true);
		resultSpec.setIncludeTagGuids(true);
		resultSpec.setIncludeNotebookGuid(true);
		resultSpec.setIncludeTitle(true);

		out.println("notebook" + csvSeparator + "title" + csvSeparator
				+ "created" + csvSeparator + "updated" + csvSeparator
				+ "author" + csvSeparator + "placename" + csvSeparator
				+ "latitude" + csvSeparator + "longitude");

		List<Notebook> notebooks = noteStoreClient.listNotebooks();
		for (Notebook nb : notebooks) {
			notebookNames.put(nb.getGuid(), nb.getName());
		}

		List<Tag> tagList = noteStoreClient.listTags();
		for (Tag tag : tagList) {
			tags.put(tag.getGuid(), tag.getName());
		}

		int offset = 0;
		int counter = 0;
		int total = 0;
		int current = 0;
		boolean hasMore = true;

		while (hasMore) {
			NotesMetadataList metadataList = noteStoreClient.findNotesMetadata(
					new NoteFilter(), offset, 10000, resultSpec);

			total = metadataList.getTotalNotes();
			current = metadataList.getNotes().size();

			for (NoteMetadata meta : metadataList.getNotes()) {
				counter = counter + 1;
				String nbGuid = meta.getNotebookGuid();
				String nbName = null;
				if (notebookNames.containsKey(nbGuid)) {
					nbName = notebookNames.get(nbGuid);
				} else {
					Notebook nb = noteStoreClient.getNotebook(nbGuid);
					nbName = nb.getName();
					notebookNames.put(nbGuid, nbName);
				}
				out.print(nbName + csvSeparator);

				out.print(meta.getTitle().replaceAll(",", " ") + csvSeparator);
				out.print(new Date(meta.getCreated()) + csvSeparator);
				out.print(new Date(meta.getUpdated()) + csvSeparator);

				String author = meta.getAttributes().getAuthor();
				String placeName = meta.getAttributes().getPlaceName();

				out.print((author == null ? "" : author) + csvSeparator);
				out.print((placeName == null ? "" : placeName) + csvSeparator);

				out.print(meta.getAttributes().getLatitude() + csvSeparator);
				out.print(meta.getAttributes().getLongitude() + csvSeparator);

				if (meta.isSetTagGuids()) {
					for (String tagGuid : meta.getTagGuids()) {
						String tagName = null;
						if (tags.containsKey(tagGuid)) {
							tagName = tags.get(tagGuid);
						} else {
							Tag tag = noteStoreClient.getTag(tagGuid);
							tagName = tag.getName();
							tags.put(tagGuid, tagName);
						}
						out.print(tagName + csvSeparator);
					}
				}

				out.println();
			}
			if (counter >= total) {
				hasMore = false;
			} else {
				offset = counter;
			}
		}

	}

	public interface MyNoteStoreClient {
		Tag getTag(String tagGuid) throws Exception;

		Notebook getNotebook(String nbGuid) throws Exception;

		NotesMetadataList findNotesMetadata(NoteFilter noteFilter, int offset,
				int maxNotes, NotesMetadataResultSpec resultSpec)
				throws Exception;

		List<Tag> listTags() throws Exception;

		List<Notebook> listNotebooks() throws Exception;
	}

	public class CloudNoteStoreClient implements MyNoteStoreClient {

		private NoteStoreClient noteStoreClient;

		public CloudNoteStoreClient(EvernoteAuth auth) throws Exception {
			noteStoreClient = new ClientFactory(auth).createNoteStoreClient();
		}

		@Override
		public Tag getTag(String tagGuid) throws Exception {
			return noteStoreClient.getTag(tagGuid);
		}

		@Override
		public Notebook getNotebook(String nbGuid) throws Exception {
			return noteStoreClient.getNotebook(nbGuid);
		}

		@Override
		public NotesMetadataList findNotesMetadata(NoteFilter noteFilter,
				int offset, int maxNotes, NotesMetadataResultSpec resultSpec)
				throws Exception {
			return noteStoreClient.findNotesMetadata(noteFilter, offset, maxNotes, resultSpec);
		}

		@Override
		public List<Tag> listTags() throws Exception {
			return noteStoreClient.listTags();
		}

		@Override
		public List<Notebook> listNotebooks() throws Exception {
			return noteStoreClient.listNotebooks();
		}

	}

	public class BusinessNoteStoreClient implements MyNoteStoreClient {

		private NoteStore.Client noteStoreClient;
		private String accessToken;
		
		public BusinessNoteStoreClient(String accessToken) throws Exception {
			this.accessToken = accessToken;
			THttpClient userStoreTrans = new THttpClient(userStoreUrl);
			TBinaryProtocol userStoreProt = new TBinaryProtocol(userStoreTrans);
			UserStore.Client userStore = new UserStore.Client(userStoreProt,
					userStoreProt);
			AuthenticationResult authResult = userStore
					.authenticateToBusiness(accessToken);
			String bNoteStoreUrl = authResult.getNoteStoreUrl();

			THttpClient noteStoreHttpClient = new THttpClient(bNoteStoreUrl);
			TBinaryProtocol bNoteStoreProtocol = new TBinaryProtocol(
					noteStoreHttpClient);
			noteStoreClient = new NoteStore.Client(
					bNoteStoreProtocol);
		}
		
		@Override
		public Tag getTag(String tagGuid) throws Exception {
			return noteStoreClient.getTag(accessToken, tagGuid);
		}

		@Override
		public Notebook getNotebook(String nbGuid) throws Exception {
			return noteStoreClient.getNotebook(accessToken, nbGuid);
		}

		@Override
		public NotesMetadataList findNotesMetadata(NoteFilter noteFilter,
				int offset, int maxNotes, NotesMetadataResultSpec resultSpec)
				throws Exception {
			return noteStoreClient.findNotesMetadata(accessToken, noteFilter, offset, maxNotes, resultSpec);
		}

		@Override
		public List<Tag> listTags() throws Exception {
			return noteStoreClient.listTags(accessToken);
		}

		@Override
		public List<Notebook> listNotebooks() throws Exception {
			return noteStoreClient.listNotebooks(accessToken);
		}
		
	}
	
}
